﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Diagnostics;
using SimpleIR;

namespace InterpreterCore
{
	/// <summary>
	/// States the debugger can be in.
	/// </summary>
	internal enum DebugState { DebugRun, DebugBreakAt, DebugStep, DebugStepInto };


	/// <summary>
	/// This class contains a bunch of static/global accessors for states of the analysis that get used over wide parts of the analysis.
	/// Also serves as the launch point for various analysis phases and queries from external code.
	/// </summary>
	public static class GlobalInterpreterState
	{
		/// <summary>
		/// The directory name of the executable that we are executing.
		/// </summary>
		internal static string ExecutableDir;

		/// <summary>
		/// The directory name where we can output the temp data.
		/// </summary>
		internal static string DumpDir;

		/// <summary>
		/// The current timestep of the execution.
		/// </summary>
		private static ulong s_currenttime;

		/// <summary>
		/// The current line of the execution.
		/// </summary>
		private static int s_currentline;

		/// <summary>
		/// The current source code position.
		/// </summary>
		private static IRSourceLocation s_currentsourceloc;

		private static void ResetRuntimeCtrs()
		{
			s_currenttime = 0;
			s_currentline = -1;
			s_currentsourceloc = null;
		}

		/// <summary>
		/// The program we are analyzing
		/// </summary>
		private static IRProgram s_prog;

		public static void LoadProg(IRProgram p, string plocation, string dumpdir, string tracefile)
		{
			ExecutableDir = plocation;
			DumpDir = dumpdir;

			s_prog = p;
			List<IRStaticField> fsl = new List<IRStaticField>();
			p.NameTable.FillVectorWithAllStaticFields(fsl);

			MemoryState.InitializeMemory();
			FileStateAndSystem.InitializeFileSystem();
			TracingSystem.InitizeTracing(tracefile);

			ResetRuntimeCtrs();
			ResetGlobalState(fsl);
		}

		public static void ExecuteMain(string[] args)
		{
			IRStaticInvoke maininv = s_prog.InvokeTable.GetMainInvoke();
			IRInvokeIdentity mainID = s_prog.InvokeTable.LookupInvokeIdentity(maininv.DefClass, maininv);
			IRBodyDef mainBody = s_prog.MethodBodyTable.GetBodyForInvokeExactSigMatch(mainID);

			LocalCallFrame mlcf = new LocalCallFrame(DebugState.DebugStep, mainBody);
			mlcf.InitializeMain(args, mainBody.Body.BasicBlockList[0].InScopeAtEntry, mainBody.Body.BasicBlockList[0].LocalsToTypeMap);

			GlobalInterpreterState.PushCallOntoStack(mlcf);
			LocalExecution.SimulateBody();
		}

		internal static void SerializeForHeapDump(System.IO.TextWriter dumpstr)
		{
			dumpstr.Write(String.Format("GlobalState\n Time={0} Line={1} CSCount={2} StaticCount={3}\n", s_currenttime, s_currentline, s_activationrecords.Count, s_staticmap.Count));

			for(int i = 0; i < s_activationrecords.Count; ++i)
			{
				s_activationrecords[i].SerializeForHeapDump(dumpstr);
				dumpstr.WriteLine();
			}

			dumpstr.Write("Statics={");
			bool first = true;
			foreach(KeyValuePair<IRStaticField, CHomeLocation> vmp in s_staticmap)
			{
				if(!first)
					dumpstr.Write(',');
				first = false;

				dumpstr.Write(String.Format("{0}::{1}*{2}", vmp.Key.DefinedInClass.TypeID, vmp.Key.FieldName, vmp.Value.Address));
			}
			dumpstr.Write("}\n");
		}

		internal static void SerializeForDGMLDisplay(System.Xml.XmlWriter nodexw, System.Xml.XmlWriter edgexw, bool currentframeonly)
		{
			foreach(KeyValuePair<IRStaticField, CHomeLocation> vmp in s_staticmap)
				vmp.Value.SerializeForDGMLDisplay(nodexw, edgexw, vmp.Key.ToString());

			if(currentframeonly)
				s_activationrecords.Last().SerializeForDGMLDisplay(nodexw, edgexw, -1);
			else
			{
				for(int i = 0; i < s_activationrecords.Count; ++i)
					s_activationrecords[i].SerializeForDGMLDisplay(nodexw, edgexw, i);
			}
		}

		internal static void ParseAndGlobalState(System.IO.TextReader rdr, DebugState indbg)
		{
			string infoline = rdr.ReadLine();
			Dictionary<string, string> ipkvp = TracingSystem.ParseKeyValuePairs(infoline);
			s_currenttime = UInt64.Parse(ipkvp["Time"]);
			s_currentline = Int32.Parse(ipkvp["Line"]);

			int cscount = Int32.Parse(ipkvp["CSCount"]);
			int stccount = Int32.Parse(ipkvp["StaticCount"]);

			s_activationrecords.Clear();
			for(int i = 0; i < cscount; ++i)
			{
				LocalCallFrame lcf = LocalCallFrame.ParseCallFrame(rdr, indbg);
				s_activationrecords.Add(lcf);
			}

			LocalCallFrame alcf = s_activationrecords.Last(); 
			IRByteCode cbytec = alcf.MethodBody.Body.GetBlockWithID(alcf.CurrentBlock).Operations[alcf.CurrentOp];
			s_currentsourceloc = cbytec.SourceLocationInfo;

			string varsline = rdr.ReadLine();
			string[] vps = varsline.Substring(9, varsline.Length - 10).Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
			s_staticmap.Clear();
			for(int i = 0; i < vps.Length; ++i)
			{
				int ipos = vps[i].IndexOf('*');
				int spos = vps[i].IndexOf("::");

				int typeid = Int32.Parse(vps[i].Substring(0, spos));
				IRType sft = GlobalInterpreterState.TProg.TypeTable.LookupTypeForID(typeid);
				string name = vps[i].Substring(spos + 2, ipos - (spos + 2));
				IRStaticField sf = GlobalInterpreterState.TProg.NameTable.LookupStaticField(sft, name);

				int address = Int32.Parse(vps[i].Substring(ipos + 1));
				CHomeLocation hloc = (CHomeLocation)MemoryState.ResolveRawAddress(address);

				s_staticmap.Add(sf, hloc);
			}
		}

		/// <summary>
		/// Add all the addresses reachable from the statics into reachaddr.
		/// </summary>
		internal static void WalkAllReachableFromRoots(HashSet<int> reachaddr)
		{
			Queue<int> toproc = new Queue<int>();
			foreach(CHomeLocation chl in s_staticmap.Values)
			{
				toproc.Enqueue(chl.Address);
				reachaddr.Add(chl.Address);
			}

			while(toproc.Count != 0)
			{
				int addr = toproc.Dequeue();
				CLocation ll = MemoryState.ResolveRawAddress(addr);

				IEnumerator<int> refenum = ll.EnumerateTrgtAddresses().GetEnumerator();
				while(refenum.MoveNext())
				{
					if(refenum.Current != -1 && !reachaddr.Contains(refenum.Current))
					{
						toproc.Enqueue(refenum.Current);
						reachaddr.Add(refenum.Current);
					}
				}
			}
		}

		/// <summary>
		/// Get the Program that we are analyzing.
		/// </summary>
		internal static IRProgram TProg { get { return s_prog; } }

		/// <summary>
		/// Get the current program time.
		/// </summary>
		internal static ulong TCurrentTime { get { return s_currenttime; } }

		/// <summary>
		/// Advance the time in the program one step
		/// </summary>
		internal static void IncrementTime()
		{ s_currenttime++; }

		/// <summary>
		/// Get the current program line.
		/// </summary>
		internal static int TCurrentLine { get { return s_currentline; } }

		/// <summary>
		/// Update the current line to a new value
		/// </summary>
		internal static void SetCurrentLine(int line, IRSourceLocation csloc)
		{
			s_currentline = line;
			s_currentsourceloc = csloc;
		}

		internal static void ExitProgram(int code)
		{
			if(code == 0)
				Console.WriteLine("Success!!");
			else
				Console.WriteLine("Failed!!");
		}

		////////////////////////////////
		//Global state and activation records 

		private static void ResetGlobalState(List<IRStaticField> fsl)
		{
			s_staticmap = new Dictionary<IRStaticField, CHomeLocation>();

			for(int i = 0; i < fsl.Count; ++i)
			{
				CHomeLocation sfhl = MemoryState.AllocateStaticHomeLocation(fsl[i], fsl[i].FieldType);
				s_staticmap.Add(fsl[i], sfhl);
			}

			s_activationrecords = new List<LocalCallFrame>();
		}

		private static Dictionary<IRStaticField, CHomeLocation> s_staticmap;

		private static List<LocalCallFrame> s_activationrecords;

		/// <summary>
		/// Get the map from static field names to the locations the values are stored.
		/// </summary>
		internal static Dictionary<IRStaticField, CHomeLocation> GlobalFieldsMap
		{ get { return s_staticmap; } }

		/// <summary>
		/// Get the current local execution frame
		/// </summary>
		internal static LocalCallFrame ActiveCallFrame
		{ get { return s_activationrecords[s_activationrecords.Count - 1]; } }

		/// <summary>
		/// Get the caller local execution frame
		/// </summary>
		internal static LocalCallFrame CallerCallFrame
		{ get { return s_activationrecords[s_activationrecords.Count - 2]; } }

		/// <summary>
		/// Get the list of all execution frames where the last is the current active
		/// </summary>
		/// <returns></returns>
		internal static List<LocalCallFrame> GetCallFrameList()
		{ return s_activationrecords; }

		/// <summary>
		/// Push a new local activation frame onto the stack.
		/// </summary>
		internal static void PushCallOntoStack(LocalCallFrame lcf)
		{ s_activationrecords.Add(lcf); }

		/// <summary>
		/// Pop the most recent actication frame off the stack.
		/// </summary>
		internal static void PopCallOffStack()
		{ s_activationrecords.RemoveAt(s_activationrecords.Count - 1); }

		////////////////////////////////
		//Debugging messages

		/// <summary>
		/// The line at which the debbuger breakpoint is set
		/// </summary>
		internal static int BreakStatementID = -1;
	}
}